

<!DOCTYPE html>
<html lang="zh-CN" data-default-color-scheme=auto>



<head>
  <meta charset="UTF-8">
  <link rel="apple-touch-icon" sizes="76x76" href="/img/fluid.png">
  <link rel="icon" href="/img/fluid.png">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, shrink-to-fit=no">
  <meta http-equiv="x-ua-compatible" content="ie=edge">
  
  <meta name="theme-color" content="#2f4154">
  <meta name="author" content="深海火锅店">
  <meta name="keywords" content="">
  
    <meta name="description" content="Seata概念事务的ACID原则  原子性 事务中的所有操作，要么全部成功,要么全部失败  一致性 要保证数据库内部完整性约束、声明性约束  隔离性 对同一资源操作的事务不能同时发生  持久性 对数据库做的一切修改将永久保存,不管是否出现故障   案例微服务下单业务，在下单时会调用订单服务，创建订单并写入数据库。然后订单服务调用账户服务和库存服务:  账户服务负责扣减用户余额 库存服务负责扣减商品">
<meta property="og:type" content="article">
<meta property="og:title" content="Seata">
<meta property="og:url" content="http://example.com/2024/04/16/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/Java/%E5%AD%A6%E4%B9%A0/%E6%A1%86%E6%9E%B6/Seata/Seata/index.html">
<meta property="og:site_name" content="Hexo">
<meta property="og:description" content="Seata概念事务的ACID原则  原子性 事务中的所有操作，要么全部成功,要么全部失败  一致性 要保证数据库内部完整性约束、声明性约束  隔离性 对同一资源操作的事务不能同时发生  持久性 对数据库做的一切修改将永久保存,不管是否出现故障   案例微服务下单业务，在下单时会调用订单服务，创建订单并写入数据库。然后订单服务调用账户服务和库存服务:  账户服务负责扣减用户余额 库存服务负责扣减商品">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240410224007195.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240410224108029.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240411202324161.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240411214609610.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240411220655902.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240411220800785.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240411221346303.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240411221519897.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240411221600202.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240411221941042.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240411222047176.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240411222133028.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240411222243057.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240411223154817.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240411223721605.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412070658420.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412071007499.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412071132954.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412071236353.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412071551150.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412071916161.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412071937524.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412072014359.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412072736505.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412073342542.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412073612996.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412073639729.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412073752159.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412075025182.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412075111494.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412075313338.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412080441953.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412080512334.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412081358601.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412081715928.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412210846377.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412211854987.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/QQ%E5%9B%BE%E7%89%8720240412212956.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240413063600514.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240413063809543.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240413065417807.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240413070301607.png">
<meta property="og:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240413205630731.png">
<meta property="article:published_time" content="2024-04-16T00:31:00.000Z">
<meta property="article:modified_time" content="2024-04-15T00:30:11.545Z">
<meta property="article:author" content="深海火锅店">
<meta property="article:tag" content="Seata">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240410224007195.png">
  
  
    <meta name="referrer" content="no-referrer-when-downgrade">
  
  
  <title>Seata - Hexo</title>

  <link  rel="stylesheet" href="https://lib.baomitu.com/twitter-bootstrap/4.6.1/css/bootstrap.min.css" />



  <link  rel="stylesheet" href="https://lib.baomitu.com/github-markdown-css/4.0.0/github-markdown.min.css" />

  <link  rel="stylesheet" href="https://lib.baomitu.com/hint.css/2.7.0/hint.min.css" />

  <link  rel="stylesheet" href="https://lib.baomitu.com/fancybox/3.5.7/jquery.fancybox.min.css" />



<!-- 主题依赖的图标库，不要自行修改 -->
<!-- Do not modify the link that theme dependent icons -->

<link rel="stylesheet" href="//at.alicdn.com/t/font_1749284_hj8rtnfg7um.css">



<link rel="stylesheet" href="//at.alicdn.com/t/font_1736178_lbnruvf0jn.css">


<link  rel="stylesheet" href="/css/main.css" />


  <link id="highlight-css" rel="stylesheet" href="/css/highlight.css" />
  
    <link id="highlight-css-dark" rel="stylesheet" href="/css/highlight-dark.css" />
  




  <script id="fluid-configs">
    var Fluid = window.Fluid || {};
    Fluid.ctx = Object.assign({}, Fluid.ctx)
    var CONFIG = {"hostname":"example.com","root":"/","version":"1.9.3","typing":{"enable":true,"typeSpeed":70,"cursorChar":"_","loop":false,"scope":[]},"anchorjs":{"enable":true,"element":"h1,h2,h3,h4,h5,h6","placement":"left","visible":"hover","icon":""},"progressbar":{"enable":true,"height_px":3,"color":"#29d","options":{"showSpinner":false,"trickleSpeed":100}},"code_language":{"enable":true,"default":"TEXT"},"copy_btn":true,"image_caption":{"enable":true},"image_zoom":{"enable":true,"img_url_replace":["",""]},"toc":{"enable":true,"placement":"right","headingSelector":"h1,h2,h3,h4,h5,h6","collapseDepth":0},"lazyload":{"enable":true,"loading_img":"/img/loading.gif","onlypost":false,"offset_factor":2},"web_analytics":{"enable":false,"follow_dnt":true,"baidu":null,"google":null,"gtag":null,"tencent":{"sid":null,"cid":null},"woyaola":null,"cnzz":null,"leancloud":{"app_id":null,"app_key":null,"server_url":null,"path":"window.location.pathname","ignore_local":false}},"search_path":"/local-search.xml"};

    if (CONFIG.web_analytics.follow_dnt) {
      var dntVal = navigator.doNotTrack || window.doNotTrack || navigator.msDoNotTrack;
      Fluid.ctx.dnt = dntVal && (dntVal.startsWith('1') || dntVal.startsWith('yes') || dntVal.startsWith('on'));
    }
  </script>
  <script  src="/js/utils.js" ></script>
  <script  src="/js/color-schema.js" ></script>
  


  
<meta name="generator" content="Hexo 6.3.0"></head>


<body>
  

  <header>
    

<div class="header-inner" style="height: 70vh;">
  <nav id="navbar" class="navbar fixed-top  navbar-expand-lg navbar-dark scrolling-navbar">
  <div class="container">
    <a class="navbar-brand" href="/">
      <strong>Fluid</strong>
    </a>

    <button id="navbar-toggler-btn" class="navbar-toggler" type="button" data-toggle="collapse"
            data-target="#navbarSupportedContent"
            aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <div class="animated-icon"><span></span><span></span><span></span></div>
    </button>

    <!-- Collapsible content -->
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav ml-auto text-center">
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/">
                <i class="iconfont icon-home-fill"></i>
                首页
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/archives/">
                <i class="iconfont icon-archive-fill"></i>
                归档
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/categories/">
                <i class="iconfont icon-category-fill"></i>
                分类
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/tags/">
                <i class="iconfont icon-tags-fill"></i>
                标签
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/about/">
                <i class="iconfont icon-user-fill"></i>
                关于
              </a>
            </li>
          
        
        
          <li class="nav-item" id="search-btn">
            <a class="nav-link" target="_self" href="javascript:;" data-toggle="modal" data-target="#modalSearch" aria-label="Search">
              &nbsp;<i class="iconfont icon-search"></i>&nbsp;
            </a>
          </li>
          
        
        
          <li class="nav-item" id="color-toggle-btn">
            <a class="nav-link" target="_self" href="javascript:;" aria-label="Color Toggle">&nbsp;<i
                class="iconfont icon-dark" id="color-toggle-icon"></i>&nbsp;</a>
          </li>
        
      </ul>
    </div>
  </div>
</nav>

  

<div id="banner" class="banner" parallax=true
     style="background: url('/img/background.jpg') no-repeat center center; background-size: cover;">
  <div class="full-bg-img">
    <div class="mask flex-center" style="background-color: rgba(0, 0, 0, 0.3)">
      <div class="banner-text text-center fade-in-up">
        <div class="h2">
          
            <span id="subtitle" data-typed-text="Seata"></span>
          
        </div>

        
          
  <div class="mt-3">
    
    
      <span class="post-meta">
        <i class="iconfont icon-date-fill" aria-hidden="true"></i>
        <time datetime="2024-04-16 08:31" pubdate>
          2024年4月16日 早上
        </time>
      </span>
    
  </div>

  <div class="mt-1">
    
      <span class="post-meta mr-2">
        <i class="iconfont icon-chart"></i>
        
          21k 字
        
      </span>
    

    
      <span class="post-meta mr-2">
        <i class="iconfont icon-clock-fill"></i>
        
        
        
          176 分钟
        
      </span>
    

    
    
  </div>


        
      </div>

      
    </div>
  </div>
</div>

</div>

  </header>

  <main>
    
      

<div class="container-fluid nopadding-x">
  <div class="row nomargin-x">
    <div class="side-col d-none d-lg-block col-lg-2">
      

    </div>

    <div class="col-lg-8 nopadding-x-md">
      <div class="container nopadding-x-md" id="board-ctn">
        <div id="board">
          <article class="post-content mx-auto">
            <!-- SEO header -->
            <h1 style="display: none">Seata</h1>
            
            
              <div class="markdown-body">
                
                <h1 id="Seata"><a href="#Seata" class="headerlink" title="Seata"></a>Seata</h1><h2 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h2><h3 id="事务的ACID原则"><a href="#事务的ACID原则" class="headerlink" title="事务的ACID原则"></a>事务的ACID原则</h3><p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240410224007195.png" srcset="/img/loading.gif" lazyload alt="image-20240410224007195"></p>
<ol>
<li><p>原子性</p>
<p>事务中的所有操作，要么全部成功,要么全部失败</p>
</li>
<li><p>一致性</p>
<p>要保证数据库内部完整性约束、声明性约束</p>
</li>
<li><p>隔离性</p>
<p>对同一资源操作的事务不能同时发生</p>
</li>
<li><p>持久性</p>
<p>对数据库做的一切修改将永久保存,不管是否出现故障</p>
</li>
</ol>
<h3 id="案例"><a href="#案例" class="headerlink" title="案例"></a>案例</h3><p>微服务下单业务，在下单时会调用订单服务，创建订单并写入数据库。然后订单服务调用账户服务和库存服务:</p>
<ul>
<li>账户服务负责扣减用户余额</li>
<li>库存服务负责扣减商品库存</li>
</ul>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240410224108029.png" srcset="/img/loading.gif" lazyload alt="image-20240410224108029"></p>
<blockquote>
<p>我们希望<code>创建订单</code>、<code>扣减余额</code>、<code>扣件商品库存</code>三个操作<code>同时成功或者失败</code></p>
</blockquote>
<p><strong>这里新建三个数据库</strong></p>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240411202324161.png" srcset="/img/loading.gif" lazyload alt="image-20240411202324161"></p>
<p><strong>sentinel_account</strong></p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">create</span> <span class="hljs-keyword">table</span> account_tbl<br>(<br>    id      <span class="hljs-type">int</span> auto_increment comment <span class="hljs-string">&#x27;账户编号&#x27;</span><br>        <span class="hljs-keyword">primary</span> key,<br>    user_id <span class="hljs-type">varchar</span>(<span class="hljs-number">255</span>)               <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;用户编号&#x27;</span>,<br>    money   <span class="hljs-type">int</span>(<span class="hljs-number">11</span>) unsigned <span class="hljs-keyword">default</span> <span class="hljs-number">0</span> <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;余额&#x27;</span><br>)<br>    comment <span class="hljs-string">&#x27;账户表&#x27;</span> charset <span class="hljs-operator">=</span> utf8<br>                     row_format <span class="hljs-operator">=</span> COMPACT;<br>                     <br><span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> sentinel_account.account_tbl (id, user_id, money) <span class="hljs-keyword">VALUES</span> (<span class="hljs-number">1</span>, <span class="hljs-string">&#x27;user202103032042012&#x27;</span>, <span class="hljs-number">1000</span>);<br></code></pre></td></tr></table></figure>

<p><strong>sentinel_order</strong></p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">create</span> <span class="hljs-keyword">table</span> order_tbl<br>(<br>    id             <span class="hljs-type">int</span> auto_increment comment <span class="hljs-string">&#x27;订单编号&#x27;</span><br>        <span class="hljs-keyword">primary</span> key,<br>    user_id        <span class="hljs-type">varchar</span>(<span class="hljs-number">255</span>)  <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;用户编号&#x27;</span>,<br>    commodity_code <span class="hljs-type">varchar</span>(<span class="hljs-number">255</span>)  <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;商品编码&#x27;</span>,<br>    count          <span class="hljs-type">int</span> <span class="hljs-keyword">default</span> <span class="hljs-number">0</span> <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;数量&#x27;</span>,<br>    money          <span class="hljs-type">int</span> <span class="hljs-keyword">default</span> <span class="hljs-number">0</span> <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;金额&#x27;</span><br>)<br>    comment <span class="hljs-string">&#x27;订单表&#x27;</span> charset <span class="hljs-operator">=</span> utf8<br>                     row_format <span class="hljs-operator">=</span> COMPACT;<br></code></pre></td></tr></table></figure>

<p><strong>sentinel_storage</strong></p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">create</span> <span class="hljs-keyword">table</span> storage_tbl<br>(<br>    id             <span class="hljs-type">int</span> auto_increment comment <span class="hljs-string">&#x27;库存编号&#x27;</span><br>        <span class="hljs-keyword">primary</span> key,<br>    commodity_code <span class="hljs-type">varchar</span>(<span class="hljs-number">255</span>)               <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;商品编码&#x27;</span>,<br>    count          <span class="hljs-type">int</span>(<span class="hljs-number">11</span>) unsigned <span class="hljs-keyword">default</span> <span class="hljs-number">0</span> <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;库存数量&#x27;</span>,<br>    <span class="hljs-keyword">constraint</span> commodity_code<br>        <span class="hljs-keyword">unique</span> (commodity_code)<br>)<br>    comment <span class="hljs-string">&#x27;库存表&#x27;</span> charset <span class="hljs-operator">=</span> utf8<br>                     row_format <span class="hljs-operator">=</span> COMPACT;<br>                     <br><span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> sentinel_storage.storage_tbl (id, commodity_code, count) <span class="hljs-keyword">VALUES</span> (<span class="hljs-number">1</span>, <span class="hljs-string">&#x27;100202003032041&#x27;</span>, <span class="hljs-number">10</span>);<br></code></pre></td></tr></table></figure>

<blockquote>
<p>基础项目的<a target="_blank" rel="noopener" href="https://gitee.com/tongstyle/seata-demo">链接</a></p>
</blockquote>
<p><strong>调用<code>client</code>实现<code>扣余额</code>、<code>扣库存</code></strong></p>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240411214609610.png" srcset="/img/loading.gif" lazyload alt="image-20240411214609610"></p>
<blockquote>
<p>使用<code>ApiFox</code>测试订单创建请求</p>
</blockquote>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240411220655902.png" srcset="/img/loading.gif" lazyload alt="image-20240411220655902"></p>
<blockquote>
<p>看看数据，发现该商品还剩下10个</p>
</blockquote>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240411220800785.png" srcset="/img/loading.gif" lazyload alt="image-20240411220800785"></p>
<blockquote>
<p>我们直接买20个，正常来讲<code>库存不足</code>应该要<code>失败</code>，这次用户要跟账户表保持一致</p>
</blockquote>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240411221346303.png" srcset="/img/loading.gif" lazyload alt="image-20240411221346303"></p>
<blockquote>
<p>报错</p>
</blockquote>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240411221519897.png" srcset="/img/loading.gif" lazyload alt="image-20240411221519897"></p>
<blockquote>
<p>刷新之后发现，库存表数量没变<code>(扣减失败)</code>，但是账户表<code>金额</code>被扣了</p>
</blockquote>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240411221600202.png" srcset="/img/loading.gif" lazyload alt="image-20240411221600202"></p>
<blockquote>
<p><strong>它们每个服务是独立的</strong>，各自提交自己的事务，所以并没有达成事务的一致</p>
</blockquote>
<p><strong><code>总结：在分布式系统下，一个业务跨越多个服务或数据源，每个服务都是一个分支事务， 要保证所有分支事务最终状态一致,这样的事务就是分布式事务。</code></strong></p>
<h2 id="理论知识"><a href="#理论知识" class="headerlink" title="理论知识"></a>理论知识</h2><h3 id="CAP定理"><a href="#CAP定理" class="headerlink" title="CAP定理"></a>CAP定理</h3><p>1998年，加州大学的计算机科学家Eric Brewer提出，分布式系统有三个指标:</p>
<ol>
<li>Consistency (<code>一致性</code>)</li>
<li>Availability (<code>可用性</code>)</li>
<li>Partition tolerance (<code>分区容错性</code>)</li>
</ol>
<p>Eric Brewer说，分布式系统无法<code>同时满足</code>这三个指标。这个结论就叫做<code>CAP定理</code>。</p>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240411221941042.png" srcset="/img/loading.gif" lazyload alt="image-20240411221941042"></p>
<h4 id="一致性"><a href="#一致性" class="headerlink" title="一致性"></a>一致性</h4><p>Consistency (一致性) :用户访问分布式系统中的任意节点,得到的数据必须一致</p>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240411222047176.png" srcset="/img/loading.gif" lazyload alt="image-20240411222047176"></p>
<h4 id="可用性"><a href="#可用性" class="headerlink" title="可用性"></a>可用性</h4><p>Availability (可用性) :用户访问集群中的任意健康节点,必须能得到响应，而不是超时或拒绝</p>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240411222133028.png" srcset="/img/loading.gif" lazyload alt="image-20240411222133028"></p>
<h4 id="分区容错"><a href="#分区容错" class="headerlink" title="分区容错"></a>分区容错</h4><p>Partition (分区) :因为网络故障或其它原因导致分布式系统中的部分节点与其它节点失去连接，形成独立分区。</p>
<p>Tolerance (容错) :在集群出现分区时,整个系统也要持续对外提供服务</p>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240411222243057.png" srcset="/img/loading.gif" lazyload alt="image-20240411222243057"></p>
<h4 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h4><p>分布式系统节点通过网络连接，一定会出现分区问题(P)，当分区出现时，系统的一致性(C)和可用性(A)就无法同时满足</p>
<h4 id="思考"><a href="#思考" class="headerlink" title="思考"></a>思考</h4><p>elasticsearch集群是CP还是AP?</p>
<p>ES集群出现分区时，故障节点会被剔除集群，数据分片会重新分配到其它节点，保证数据一致。因此是低可用性，高一致性，属于CP</p>
<h3 id="BASE理论"><a href="#BASE理论" class="headerlink" title="BASE理论"></a>BASE理论</h3><p>BASE理论是对CAP的一种解决思路,包含三个思想:<br><code>Basically Available</code>(<strong><code>基本可用</code></strong>) :分布式系统在出现故障时，允许损失部分可用性，即保证核心可用。<br><code>SoftState</code> (<strong><code>软状态</code></strong>) :在一定时间内，允许出现中间状态，比如临时的不一致状态。<br><code>Eventually Consistent</code> (<strong><code>最终一致性</code></strong>) :虽然无法保证强-致性,但是在软状态结束后，最终达到数据一致。</p>
<p>而分布式事务最大的问题是各个子事务的一致性问题，因此可以<strong>借鉴CAP定理和BASE理论</strong>:<br><code>AP模式</code>:各子事务分别执行和提交,允许出现结果不一致,然后采用弥补措施<code>恢复数据</code>即可，实现<code>最终一致</code>。</p>
<p><code>CP模式</code>:各个子事务执行后互相等待，同时提交,同时回滚,达成<code>强一致</code>。但事务等待过程中,处于弱可用状态。因为会锁定资源导致无法访问</p>
<h4 id="分布式事务模型"><a href="#分布式事务模型" class="headerlink" title="分布式事务模型"></a>分布式事务模型</h4><p>解决分布式事务，各个子系统之间必须能感知到彼此的事务状态，才能保证状态一致，因此需要一个事务协调者来协调每一个事务的参与者(子系统事务)。</p>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240411223154817.png" srcset="/img/loading.gif" lazyload alt="image-20240411223154817"></p>
<blockquote>
<p>这里的子系统事务，称为分支事务;有关联的各个分支事务在一起称为全局事务</p>
</blockquote>
<h2 id="初识Seata"><a href="#初识Seata" class="headerlink" title="初识Seata"></a>初识Seata</h2><h3 id="Seata架构"><a href="#Seata架构" class="headerlink" title="Seata架构"></a>Seata架构</h3><p>Seata事务管理中有三个重要的角色:</p>
<ol>
<li>TC (Transaction Coordinator) -事务协调者:维护全局和分支事务的状态，协调全局事务提交或回滚。</li>
<li>TM (Transaction Manager) -事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务。</li>
<li>RM (Resource Manager) -资源管理器:管理分支事务处理的资源，与TC交谈以注册分支事务和报告分支事务的状态，并驱动分支事务提交或回滚。</li>
</ol>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240411223721605.png" srcset="/img/loading.gif" lazyload alt="image-20240411223721605"></p>
<p>Seata提供了四种不同的分布式事务解决方案:</p>
<ul>
<li>XA模式:强一致性分阶段事务模式，牺牲了一定的可用性，无业务侵入</li>
<li>TCC模式:最终一致的分阶段事务模式，有业务侵入</li>
<li>AT模式:最终一致的分阶段事务模式，无业务侵入，也是Seata的默认模式</li>
<li>SAGA模式:长事务模式，有业务侵入</li>
</ul>
<h3 id="安装Seata"><a href="#安装Seata" class="headerlink" title="安装Seata"></a>安装Seata</h3><h4 id="下载"><a href="#下载" class="headerlink" title="下载"></a>下载</h4><p>前往Github<a target="_blank" rel="noopener" href="https://github.com/apache/incubator-seata">下载</a>，这里选择<code>seata-server-1.7.0</code>的版本</p>
<h4 id="配置nacos为注册中心和配置中心"><a href="#配置nacos为注册中心和配置中心" class="headerlink" title="配置nacos为注册中心和配置中心"></a>配置nacos为注册中心和配置中心</h4><p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412070658420.png" srcset="/img/loading.gif" lazyload alt="image-20240412070658420"></p>
<blockquote>
<p><code>application.example.yml</code>的配置很全，我们copy需要的配置到<code>application.yml</code>即可</p>
</blockquote>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412071007499.png" srcset="/img/loading.gif" lazyload alt="image-20240412071007499"></p>
<p><strong>修改后的配置</strong></p>
<figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br></pre></td><td class="code"><pre><code class="hljs yml"><span class="hljs-comment">#  Copyright 1999-2019 Seata.io Group.</span><br><span class="hljs-comment">#</span><br><span class="hljs-comment">#  Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);</span><br><span class="hljs-comment">#  you may not use this file except in compliance with the License.</span><br><span class="hljs-comment">#  You may obtain a copy of the License at</span><br><span class="hljs-comment">#</span><br><span class="hljs-comment">#  http://www.apache.org/licenses/LICENSE-2.0</span><br><span class="hljs-comment">#</span><br><span class="hljs-comment">#  Unless required by applicable law or agreed to in writing, software</span><br><span class="hljs-comment">#  distributed under the License is distributed on an &quot;AS IS&quot; BASIS,</span><br><span class="hljs-comment">#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.</span><br><span class="hljs-comment">#  See the License for the specific language governing permissions and</span><br><span class="hljs-comment">#  limitations under the License.</span><br><br><span class="hljs-attr">server:</span><br>  <span class="hljs-attr">port:</span> <span class="hljs-number">7091</span><br><br><span class="hljs-attr">spring:</span><br>  <span class="hljs-attr">application:</span><br>    <span class="hljs-attr">name:</span> <span class="hljs-string">seata-server</span><br><br><span class="hljs-attr">logging:</span><br>  <span class="hljs-attr">config:</span> <span class="hljs-string">classpath:logback-spring.xml</span><br>  <span class="hljs-attr">file:</span><br>    <span class="hljs-attr">path:</span> <span class="hljs-string">$&#123;user.home&#125;/logs/seata</span><br>  <span class="hljs-attr">extend:</span><br>    <span class="hljs-attr">logstash-appender:</span><br>      <span class="hljs-attr">destination:</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span><span class="hljs-string">:4560</span><br>    <span class="hljs-attr">kafka-appender:</span><br>      <span class="hljs-attr">bootstrap-servers:</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span><span class="hljs-string">:9092</span><br>      <span class="hljs-attr">topic:</span> <span class="hljs-string">logback_to_logstash</span><br><br><span class="hljs-attr">console:</span><br>  <span class="hljs-attr">user:</span><br>    <span class="hljs-attr">username:</span> <span class="hljs-string">seata</span><br>    <span class="hljs-attr">password:</span> <span class="hljs-string">seata</span><br><span class="hljs-attr">seata:</span><br>  <span class="hljs-attr">config:</span><br>    <span class="hljs-comment"># support: nacos 、 consul 、 apollo 、 zk  、 etcd3</span><br>    <span class="hljs-attr">type:</span> <span class="hljs-string">nacos</span><br>    <span class="hljs-attr">nacos:</span><br>      <span class="hljs-attr">server-addr:</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span><span class="hljs-string">:8848</span><br>      <span class="hljs-attr">namespace:</span> <span class="hljs-string">d2455f6d-ed00-41ba-9915-09021bc0df6c</span><br>      <span class="hljs-comment"># group: SEATA_GROUP</span><br>      <span class="hljs-attr">group:</span> <span class="hljs-string">DEFAULT_GROUP</span><br>      <span class="hljs-attr">username:</span> <span class="hljs-string">nacos</span><br>      <span class="hljs-attr">password:</span> <span class="hljs-string">nacos</span><br>      <span class="hljs-attr">context-path:</span><br>      <span class="hljs-comment">##if use MSE Nacos with auth, mutex with username/password attribute</span><br>      <span class="hljs-comment">#access-key:</span><br>      <span class="hljs-comment">#secret-key:</span><br>      <span class="hljs-attr">data-id:</span> <span class="hljs-string">seataServer.properties</span><br>  <span class="hljs-attr">registry:</span><br>    <span class="hljs-comment"># support: nacos 、 eureka 、 redis 、 zk  、 consul 、 etcd3 、 sofa</span><br>    <span class="hljs-attr">type:</span> <span class="hljs-string">nacos</span><br>    <span class="hljs-attr">preferred-networks:</span> <span class="hljs-number">30.240</span><span class="hljs-string">.*</span><br>    <span class="hljs-attr">nacos:</span><br>      <span class="hljs-attr">application:</span> <span class="hljs-string">seata-server</span><br>      <span class="hljs-attr">server-addr:</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span><span class="hljs-string">:8848</span><br>      <span class="hljs-comment"># group: SEATA_GROUP</span><br>      <span class="hljs-attr">group:</span> <span class="hljs-string">DEFAULT_GROUP</span><br>      <span class="hljs-attr">namespace:</span> <span class="hljs-string">d2455f6d-ed00-41ba-9915-09021bc0df6c</span><br>      <span class="hljs-attr">cluster:</span> <span class="hljs-string">default</span><br>      <span class="hljs-attr">username:</span> <span class="hljs-string">nacos</span><br>      <span class="hljs-attr">password:</span> <span class="hljs-string">nacos</span><br>      <span class="hljs-attr">context-path:</span><br>      <span class="hljs-comment">##if use MSE Nacos with auth, mutex with username/password attribute</span><br>      <span class="hljs-comment">#access-key:</span><br>      <span class="hljs-comment">#secret-key:</span><br>  <span class="hljs-attr">store:</span><br>    <span class="hljs-comment"># support: file 、 db 、 redis</span><br>    <span class="hljs-attr">mode:</span> <span class="hljs-string">db</span><br><span class="hljs-comment">#  server:</span><br><span class="hljs-comment">#    service-port: 8091 #If not configured, the default is &#x27;$&#123;server.port&#125; + 1000&#x27;</span><br>  <span class="hljs-attr">security:</span><br>    <span class="hljs-attr">secretKey:</span> <span class="hljs-string">SeataSecretKey0c382ef121d778043159209298fd40bf3850a017</span><br>    <span class="hljs-attr">tokenValidityInMilliseconds:</span> <span class="hljs-number">1800000</span><br>    <span class="hljs-attr">ignore:</span><br>      <span class="hljs-attr">urls:</span> <span class="hljs-string">/,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.jpeg,/**/*.ico,/api/v1/auth/login</span><br></code></pre></td></tr></table></figure>

<p><strong>在<code>nacos</code>中新增配置</strong></p>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412071132954.png" srcset="/img/loading.gif" lazyload alt="image-20240412071132954"></p>
<blockquote>
<p>配置文件可以查看</p>
</blockquote>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412071236353.png" srcset="/img/loading.gif" lazyload alt="image-20240412071236353"></p>
<p><strong>新增配置如下</strong></p>
<blockquote>
<p>下面除了<code>db</code>外的配置都是默认的，写到nacos是为了方便后期修改，<code>db之外的配置</code>不写也没事</p>
<p>这里的<code>driverClassName</code>我用的是<code>com.mysql.cj.jdbc.Driver</code>，注意修改成自己的数据库名、账号、密码</p>
</blockquote>
<figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><code class="hljs properties"><span class="hljs-comment"># 数据存储方式，db代表数据库</span><br><span class="hljs-attr">store.mode</span>=<span class="hljs-string">db</span><br><span class="hljs-attr">store.db.datasource</span>=<span class="hljs-string">druid</span><br><span class="hljs-attr">store.db.dbType</span>=<span class="hljs-string">mysql</span><br><span class="hljs-attr">store.db.driverClassName</span>=<span class="hljs-string">com.mysql.cj.jdbc.Driver</span><br><span class="hljs-attr">store.db.url</span>=<span class="hljs-string">jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&amp;rewriteBatchedStatements=true</span><br><span class="hljs-attr">store.db.user</span>=<span class="hljs-string">root</span><br><span class="hljs-attr">store.db.password</span>=<span class="hljs-string">123456</span><br><span class="hljs-attr">store.db.minConn</span>=<span class="hljs-string">5</span><br><span class="hljs-attr">store.db.maxConn</span>=<span class="hljs-string">30</span><br><span class="hljs-attr">store.db.globalTable</span>=<span class="hljs-string">global_table</span><br><span class="hljs-attr">store.db.branchTable</span>=<span class="hljs-string">branch_table</span><br><span class="hljs-attr">store.db.queryLimit</span>=<span class="hljs-string">100</span><br><span class="hljs-attr">store.db.lockTable</span>=<span class="hljs-string">lock_table</span><br><span class="hljs-attr">store.db.maxWait</span>=<span class="hljs-string">5000</span><br><span class="hljs-comment"># 事务、日志等配置</span><br><span class="hljs-attr">server.recovery.committingRetryPeriod</span>=<span class="hljs-string">1000</span><br><span class="hljs-attr">server.recovery.asynCommittingRetryPeriod</span>=<span class="hljs-string">1000</span><br><span class="hljs-attr">server.recovery.rollbackingRetryPeriod</span>=<span class="hljs-string">1000</span><br><span class="hljs-attr">server.recovery.timeoutRetryPeriod</span>=<span class="hljs-string">1000</span><br><span class="hljs-attr">server.maxCommitRetryTimeout</span>=<span class="hljs-string">-1</span><br><span class="hljs-attr">server.maxRollbackRetryTimeout</span>=<span class="hljs-string">-1</span><br><span class="hljs-attr">server.rollbackRetryTimeoutUnlockEnable</span>=<span class="hljs-string">false</span><br><span class="hljs-attr">server.undo.logSaveDays</span>=<span class="hljs-string">7</span><br><span class="hljs-attr">server.undo.logDeletePeriod</span>=<span class="hljs-string">86400000</span><br><span class="hljs-comment"></span><br><span class="hljs-comment"># 客户端与服务端传输方式</span><br><span class="hljs-attr">transport.serialization</span>=<span class="hljs-string">seata</span><br><span class="hljs-attr">transport.compressor</span>=<span class="hljs-string">none</span><br><span class="hljs-comment"># 关闭metrics功能，提高性能</span><br><span class="hljs-attr">metrics.enabled</span>=<span class="hljs-string">false</span><br><span class="hljs-attr">metrics.registryType</span>=<span class="hljs-string">compact</span><br><span class="hljs-attr">metrics.exporterList</span>=<span class="hljs-string">prometheus</span><br><span class="hljs-attr">metrics.exporterPrometheusPort</span>=<span class="hljs-string">9898</span><br></code></pre></td></tr></table></figure>

<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412071551150.png" srcset="/img/loading.gif" lazyload alt="image-20240412071551150"></p>
<blockquote>
<p>db配置，注意这里新增一个<code>undo_log</code>表，该表的建表语句在sql中不存在</p>
<p><strong>完整sql如下</strong></p>
</blockquote>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-comment">-- -------------------------------- The script used when storeMode is &#x27;db&#x27; --------------------------------</span><br><span class="hljs-comment">-- the table to store GlobalSession data</span><br><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> IF <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">EXISTS</span> `global_table`<br>(<br>    `xid`                       <span class="hljs-type">VARCHAR</span>(<span class="hljs-number">128</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">NULL</span>,<br>    `transaction_id`            <span class="hljs-type">BIGINT</span>,<br>    `status`                    TINYINT      <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">NULL</span>,<br>    `application_id`            <span class="hljs-type">VARCHAR</span>(<span class="hljs-number">32</span>),<br>    `transaction_service_group` <span class="hljs-type">VARCHAR</span>(<span class="hljs-number">32</span>),<br>    `transaction_name`          <span class="hljs-type">VARCHAR</span>(<span class="hljs-number">128</span>),<br>    `timeout`                   <span class="hljs-type">INT</span>,<br>    `begin_time`                <span class="hljs-type">BIGINT</span>,<br>    `application_data`          <span class="hljs-type">VARCHAR</span>(<span class="hljs-number">2000</span>),<br>    `gmt_create`                DATETIME,<br>    `gmt_modified`              DATETIME,<br>    <span class="hljs-keyword">PRIMARY</span> KEY (`xid`),<br>    KEY `idx_status_gmt_modified` (`status` , `gmt_modified`),<br>    KEY `idx_transaction_id` (`transaction_id`)<br>) ENGINE <span class="hljs-operator">=</span> InnoDB<br>  <span class="hljs-keyword">DEFAULT</span> CHARSET <span class="hljs-operator">=</span> utf8mb4;<br><br><span class="hljs-comment">-- the table to store BranchSession data</span><br><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> IF <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">EXISTS</span> `branch_table`<br>(<br>    `branch_id`         <span class="hljs-type">BIGINT</span>       <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">NULL</span>,<br>    `xid`               <span class="hljs-type">VARCHAR</span>(<span class="hljs-number">128</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">NULL</span>,<br>    `transaction_id`    <span class="hljs-type">BIGINT</span>,<br>    `resource_group_id` <span class="hljs-type">VARCHAR</span>(<span class="hljs-number">32</span>),<br>    `resource_id`       <span class="hljs-type">VARCHAR</span>(<span class="hljs-number">256</span>),<br>    `branch_type`       <span class="hljs-type">VARCHAR</span>(<span class="hljs-number">8</span>),<br>    `status`            TINYINT,<br>    `client_id`         <span class="hljs-type">VARCHAR</span>(<span class="hljs-number">64</span>),<br>    `application_data`  <span class="hljs-type">VARCHAR</span>(<span class="hljs-number">2000</span>),<br>    `gmt_create`        DATETIME(<span class="hljs-number">6</span>),<br>    `gmt_modified`      DATETIME(<span class="hljs-number">6</span>),<br>    <span class="hljs-keyword">PRIMARY</span> KEY (`branch_id`),<br>    KEY `idx_xid` (`xid`)<br>) ENGINE <span class="hljs-operator">=</span> InnoDB<br>  <span class="hljs-keyword">DEFAULT</span> CHARSET <span class="hljs-operator">=</span> utf8mb4;<br><br><span class="hljs-comment">-- the table to store lock data</span><br><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> IF <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">EXISTS</span> `lock_table`<br>(<br>    `row_key`        <span class="hljs-type">VARCHAR</span>(<span class="hljs-number">128</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">NULL</span>,<br>    `xid`            <span class="hljs-type">VARCHAR</span>(<span class="hljs-number">128</span>),<br>    `transaction_id` <span class="hljs-type">BIGINT</span>,<br>    `branch_id`      <span class="hljs-type">BIGINT</span>       <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">NULL</span>,<br>    `resource_id`    <span class="hljs-type">VARCHAR</span>(<span class="hljs-number">256</span>),<br>    `table_name`     <span class="hljs-type">VARCHAR</span>(<span class="hljs-number">32</span>),<br>    `pk`             <span class="hljs-type">VARCHAR</span>(<span class="hljs-number">36</span>),<br>    `status`         TINYINT      <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">NULL</span> <span class="hljs-keyword">DEFAULT</span> <span class="hljs-string">&#x27;0&#x27;</span> COMMENT <span class="hljs-string">&#x27;0:locked ,1:rollbacking&#x27;</span>,<br>    `gmt_create`     DATETIME,<br>    `gmt_modified`   DATETIME,<br>    <span class="hljs-keyword">PRIMARY</span> KEY (`row_key`),<br>    KEY `idx_status` (`status`),<br>    KEY `idx_branch_id` (`branch_id`),<br>    KEY `idx_xid` (`xid`)<br>) ENGINE <span class="hljs-operator">=</span> InnoDB<br>  <span class="hljs-keyword">DEFAULT</span> CHARSET <span class="hljs-operator">=</span> utf8mb4;<br><br><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> IF <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">EXISTS</span> `distributed_lock`<br>(<br>    `lock_key`       <span class="hljs-type">CHAR</span>(<span class="hljs-number">20</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">NULL</span>,<br>    `lock_value`     <span class="hljs-type">VARCHAR</span>(<span class="hljs-number">20</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">NULL</span>,<br>    `expire`         <span class="hljs-type">BIGINT</span>,<br>    <span class="hljs-keyword">primary</span> key (`lock_key`)<br>) ENGINE <span class="hljs-operator">=</span> InnoDB<br>  <span class="hljs-keyword">DEFAULT</span> CHARSET <span class="hljs-operator">=</span> utf8mb4;<br><br><span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> `distributed_lock` (lock_key, lock_value, expire) <span class="hljs-keyword">VALUES</span> (<span class="hljs-string">&#x27;AsyncCommitting&#x27;</span>, <span class="hljs-string">&#x27; &#x27;</span>, <span class="hljs-number">0</span>);<br><span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> `distributed_lock` (lock_key, lock_value, expire) <span class="hljs-keyword">VALUES</span> (<span class="hljs-string">&#x27;RetryCommitting&#x27;</span>, <span class="hljs-string">&#x27; &#x27;</span>, <span class="hljs-number">0</span>);<br><span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> `distributed_lock` (lock_key, lock_value, expire) <span class="hljs-keyword">VALUES</span> (<span class="hljs-string">&#x27;RetryRollbacking&#x27;</span>, <span class="hljs-string">&#x27; &#x27;</span>, <span class="hljs-number">0</span>);<br><span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> `distributed_lock` (lock_key, lock_value, expire) <span class="hljs-keyword">VALUES</span> (<span class="hljs-string">&#x27;TxTimeoutCheck&#x27;</span>, <span class="hljs-string">&#x27; &#x27;</span>, <span class="hljs-number">0</span>);<br><br><span class="hljs-comment">-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log</span><br><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> `undo_log` (<br>  `id` <span class="hljs-type">bigint</span>(<span class="hljs-number">20</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">NULL</span> AUTO_INCREMENT,<br>  `branch_id` <span class="hljs-type">bigint</span>(<span class="hljs-number">20</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">NULL</span>,<br>  `xid` <span class="hljs-type">varchar</span>(<span class="hljs-number">100</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">NULL</span>,<br>  `context` <span class="hljs-type">varchar</span>(<span class="hljs-number">128</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">NULL</span>,<br>  `rollback_info` longblob <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">NULL</span>,<br>  `log_status` <span class="hljs-type">int</span>(<span class="hljs-number">11</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">NULL</span>,<br>  `log_created` datetime <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">NULL</span>,<br>  `log_modified` datetime <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">NULL</span>,<br>  `ext` <span class="hljs-type">varchar</span>(<span class="hljs-number">100</span>) <span class="hljs-keyword">DEFAULT</span> <span class="hljs-keyword">NULL</span>,<br>  <span class="hljs-keyword">PRIMARY</span> KEY (`id`),<br>  <span class="hljs-keyword">UNIQUE</span> KEY `ux_undo_log` (`xid`,`branch_id`)<br>) ENGINE<span class="hljs-operator">=</span>InnoDB AUTO_INCREMENT<span class="hljs-operator">=</span><span class="hljs-number">1</span> <span class="hljs-keyword">DEFAULT</span> CHARSET<span class="hljs-operator">=</span>utf8;<br></code></pre></td></tr></table></figure>

<blockquote>
<p>而且<code>业务数据库也需要增加该表</code></p>
</blockquote>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412071916161.png" srcset="/img/loading.gif" lazyload alt="image-20240412071916161"></p>
<blockquote>
<p>双击<code>seata-server.bat</code>启动即可</p>
</blockquote>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412071937524.png" srcset="/img/loading.gif" lazyload alt="image-20240412071937524"></p>
<blockquote>
<p>服务已经注册到nacos中了</p>
</blockquote>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412072014359.png" srcset="/img/loading.gif" lazyload alt="image-20240412072014359"></p>
<blockquote>
<p>注意命名空间，我们springboot的demo是<code>public</code>的，所以需要将seata的命名空间改为<code>public</code></p>
</blockquote>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412072736505.png" srcset="/img/loading.gif" lazyload alt="image-20240412072736505"></p>
<h3 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h3><blockquote>
<p>引入依赖</p>
</blockquote>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.alibaba.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-starter-alibaba-seata<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br></code></pre></td></tr></table></figure>

<blockquote>
<p>在<code>微服务</code>中新增如下配置</p>
</blockquote>
<figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">seata:</span><br>    <span class="hljs-attr">registry:</span><br>        <span class="hljs-attr">type:</span> <span class="hljs-string">nacos</span><br>        <span class="hljs-attr">nacos:</span><br>            <span class="hljs-attr">server-addr:</span> <span class="hljs-string">localhost:8848</span><br>            <span class="hljs-attr">namespace:</span> <span class="hljs-string">public</span><br>            <span class="hljs-attr">group:</span> <span class="hljs-string">DEFAULT_GROUP</span><br>            <span class="hljs-comment"># tc服务在nacos的注册名称</span><br>            <span class="hljs-attr">application:</span> <span class="hljs-string">seata-server</span><br>            <span class="hljs-attr">username:</span> <span class="hljs-string">nacos</span><br>            <span class="hljs-attr">password:</span> <span class="hljs-string">nacos</span><br>    <span class="hljs-comment"># 事务组，根据这个获取tc服务的cluster名称</span><br>    <span class="hljs-attr">tx-service-group:</span> <span class="hljs-string">seata-demo</span><br>    <span class="hljs-attr">service:</span><br>        <span class="hljs-comment"># 事务组与TC服务cluster的映射关系</span><br>        <span class="hljs-attr">vgroup-mapping:</span><br>          <span class="hljs-attr">seata-demo:</span> <span class="hljs-string">SH</span><br></code></pre></td></tr></table></figure>

<blockquote>
<p>完整的配置如下</p>
</blockquote>
<figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">server:</span><br>    <span class="hljs-attr">port:</span> <span class="hljs-number">8070</span><br><span class="hljs-attr">spring:</span><br>    <span class="hljs-attr">application:</span><br>        <span class="hljs-attr">name:</span> <span class="hljs-string">account-service</span><br>    <span class="hljs-attr">cloud:</span><br>        <span class="hljs-attr">nacos:</span><br>            <span class="hljs-attr">config:</span><br>                <span class="hljs-attr">server-addr:</span> <span class="hljs-string">localhost:8848</span><br>            <span class="hljs-attr">discovery:</span><br>                <span class="hljs-attr">server-addr:</span> <span class="hljs-string">localhost:8848</span><br>    <span class="hljs-attr">config:</span><br>        <span class="hljs-attr">import:</span> <span class="hljs-string">nacos:nacos-config-example.properties?refresh=true</span><br>    <span class="hljs-attr">datasource:</span><br>        <span class="hljs-comment"># 数据库驱动：</span><br>        <span class="hljs-attr">driver-class-name:</span> <span class="hljs-string">com.mysql.cj.jdbc.Driver</span><br>        <span class="hljs-comment"># 数据库连接地址</span><br>        <span class="hljs-attr">name:</span> <span class="hljs-string">defaultDataSource</span><br>        <span class="hljs-comment"># 数据库用户名&amp;密码：</span><br>        <span class="hljs-attr">username:</span> <span class="hljs-string">root</span><br>        <span class="hljs-attr">password:</span> <span class="hljs-number">123456</span><br>        <span class="hljs-comment"># 数据源名称</span><br>        <span class="hljs-attr">url:</span> <span class="hljs-string">jdbc:mysql://localhost:3306/sentinel_account?serverTimezone=UTC&amp;useUnicode=true&amp;characterEncoding=utf-8</span><br><span class="hljs-attr">mybatis-plus:</span><br>    <span class="hljs-attr">configuration:</span><br>        <span class="hljs-comment"># 配置mybatis-plus 打印sql日志</span><br>        <span class="hljs-attr">log-impl:</span> <span class="hljs-string">org.apache.ibatis.logging.stdout.StdOutImpl</span><br>        <span class="hljs-comment"># mybatis-plus下划线转驼峰配置，默认为true</span><br>        <span class="hljs-attr">map-underscore-to-camel-case:</span> <span class="hljs-literal">true</span><br>    <span class="hljs-comment"># xml文件路径</span><br>    <span class="hljs-attr">mapper-locations:</span> <span class="hljs-string">classpath:/mapper/**/*.xml</span><br>    <span class="hljs-comment"># 配置mybatis-plus 包路径</span><br>    <span class="hljs-attr">type-aliases-package:</span> <span class="hljs-string">com.example.account.entity</span><br><span class="hljs-attr">seata:</span><br>    <span class="hljs-attr">registry:</span><br>        <span class="hljs-attr">type:</span> <span class="hljs-string">nacos</span><br>        <span class="hljs-attr">nacos:</span><br>            <span class="hljs-attr">server-addr:</span> <span class="hljs-string">localhost:8848</span><br>            <span class="hljs-attr">namespace:</span> <span class="hljs-string">public</span><br>            <span class="hljs-attr">group:</span> <span class="hljs-string">DEFAULT_GROUP</span><br>            <span class="hljs-comment"># tc服务在nacos的注册名称</span><br>            <span class="hljs-attr">application:</span> <span class="hljs-string">seata-server</span><br>            <span class="hljs-attr">username:</span> <span class="hljs-string">nacos</span><br>            <span class="hljs-attr">password:</span> <span class="hljs-string">nacos</span><br>    <span class="hljs-comment"># 事务组，根据这个获取tc服务的cluster名称</span><br>    <span class="hljs-attr">tx-service-group:</span> <span class="hljs-string">seata-demo</span><br>    <span class="hljs-attr">service:</span><br>        <span class="hljs-comment"># 事务组与TC服务cluster的映射关系</span><br>        <span class="hljs-attr">vgroup-mapping:</span><br>          <span class="hljs-attr">seata-demo:</span> <span class="hljs-string">SH</span><br></code></pre></td></tr></table></figure>

<blockquote>
<p>如果需要锁定一个服务，我们需要确定以下四个配置</p>
<ol>
<li>命名空间</li>
<li>服务分组</li>
<li>服务名称</li>
<li>集群名称</li>
</ol>
</blockquote>
<p><strong>集群如下</strong></p>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412073342542.png" srcset="/img/loading.gif" lazyload alt="image-20240412073342542"></p>
<blockquote>
<p>再改一下<code>seata</code>的配置，配置<code>SH</code>集群</p>
</blockquote>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412073612996.png" srcset="/img/loading.gif" lazyload alt="image-20240412073612996"></p>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412073639729.png" srcset="/img/loading.gif" lazyload alt="image-20240412073639729"></p>
<blockquote>
<p>我们并没有在微服务的<code>application</code>文件中配置<code>cluster</code></p>
</blockquote>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412073752159.png" srcset="/img/loading.gif" lazyload alt="image-20240412073752159"></p>
<blockquote>
<p>并不能在<code>registry</code>下直接配置<code>cluster</code></p>
<p>正确做法是配置<code>事务组</code>，将事务分组，比如<code>account</code>、<code>order</code>、<code>storage</code>将来会被同一个<code>tc</code>集群管理，所以需要将它们配置到同一个事务组下</p>
<p>再根据<code>事务组</code>获取到映射关系，最终找到<code>集群名称</code></p>
<p>这部分涉及到了<code>seata</code>的高可用，暂时不用过多深究</p>
</blockquote>
<p><strong>启动服务</strong></p>
<p>启动微服务后，seata控命令窗口打印日志如下</p>
<figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs routeros">RM register success,message:RegisterRMRequest&#123;<span class="hljs-attribute">resourceIds</span>=<span class="hljs-string">&#x27;jdbc:mysql://localhost:3306/sentinel_account&#x27;</span>, <span class="hljs-attribute">version</span>=<span class="hljs-string">&#x27;1.7.0-native-rc2&#x27;</span>, <span class="hljs-attribute">applicationId</span>=<span class="hljs-string">&#x27;account-service&#x27;</span>, <span class="hljs-attribute">transactionServiceGroup</span>=<span class="hljs-string">&#x27;seata-demo&#x27;</span>, <span class="hljs-attribute">extraData</span>=<span class="hljs-string">&#x27;null&#x27;</span>&#125;,channel:[id: 0xc06aa8c5, L:/192.168.56.1:8091 - R:/192.168.56.1:57929],client version:1.7.0-native-rc2<br></code></pre></td></tr></table></figure>

<h2 id="动手实践"><a href="#动手实践" class="headerlink" title="动手实践"></a>动手实践</h2><h3 id="XA模式"><a href="#XA模式" class="headerlink" title="XA模式"></a>XA模式</h3><h4 id="概念-1"><a href="#概念-1" class="headerlink" title="概念"></a>概念</h4><blockquote>
<p>XA规范是x&#x2F;Open组织定义的分布式事务处理(DTP, Distributed Transaction Processing)标准，XA规范描述了全局的TM与局部的RM之间的接口，几乎所有主流的数据库都对XA规范提供了支持。XA模式是一种强一致性、低可用性的模式</p>
</blockquote>
<p><strong>正常情况</strong></p>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412075025182.png" srcset="/img/loading.gif" lazyload alt="image-20240412075025182"></p>
<p><strong>异常情况</strong></p>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412075111494.png" srcset="/img/loading.gif" lazyload alt="image-20240412075111494"></p>
<h4 id="Seata中的XA模式"><a href="#Seata中的XA模式" class="headerlink" title="Seata中的XA模式"></a>Seata中的XA模式</h4><blockquote>
<p>seata的XA模式做了一些调整,但大体相似</p>
</blockquote>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412075313338.png" srcset="/img/loading.gif" lazyload alt="image-20240412075313338"></p>
<p><strong>RM一阶段的工作</strong>:</p>
<ol>
<li>注册分支事务到TC</li>
<li>执行分支业务sq|但不提交</li>
<li>报告执行状态到TC</li>
</ol>
<p><strong>TC二阶段的工作</strong>:</p>
<ul>
<li><p>TC检测各分支事务执行状态</p>
<p>​	a.如果都成功，通知所有RM提交事务</p>
<p>​	b.如果有失败，通知所有RM回滚事务</p>
</li>
</ul>
<p><strong>RM二阶段的工作</strong>:<br>接收TC指令，提交或回滚事务</p>
<h4 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h4><p>XA模式的优点是什么?</p>
<ol>
<li>事务的强一致性,满足ACID原则。</li>
<li>常用数据库都支持，实现简单，并且没有代码侵入</li>
</ol>
<p>XA模式的缺点是什么?</p>
<ol>
<li>因为一阶段需要锁定数据库资源，等待二阶段结束才释放，性能较差</li>
<li>依赖关系型数据库实现事务(如redis就无法实现)</li>
</ol>
<h4 id="实现XA模式"><a href="#实现XA模式" class="headerlink" title="实现XA模式"></a>实现XA模式</h4><ol>
<li><p>修改<code>application.yml</code>文件，开启<code>XA</code>模式</p>
<figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">seata:</span><br>    <span class="hljs-attr">registry:</span><br>        <span class="hljs-attr">type:</span> <span class="hljs-string">nacos</span><br>        <span class="hljs-attr">nacos:</span><br>            <span class="hljs-attr">server-addr:</span> <span class="hljs-string">localhost:8848</span><br>            <span class="hljs-attr">namespace:</span> <span class="hljs-string">public</span><br>            <span class="hljs-attr">group:</span> <span class="hljs-string">DEFAULT_GROUP</span><br>            <span class="hljs-comment"># tc服务在nacos的注册名称</span><br>            <span class="hljs-attr">application:</span> <span class="hljs-string">seata-server</span><br>            <span class="hljs-attr">username:</span> <span class="hljs-string">nacos</span><br>            <span class="hljs-attr">password:</span> <span class="hljs-string">nacos</span><br>    <span class="hljs-comment"># 事务组，根据这个获取tc服务的cluster名称</span><br>    <span class="hljs-attr">tx-service-group:</span> <span class="hljs-string">seata-demo</span><br>    <span class="hljs-attr">service:</span><br>        <span class="hljs-comment"># 事务组与TC服务cluster的映射关系</span><br>        <span class="hljs-attr">vgroup-mapping:</span><br>            <span class="hljs-attr">seata-demo:</span> <span class="hljs-string">SH</span><br>	<span class="hljs-comment"># 开启XA模式</span><br>    <span class="hljs-attr">data-source-proxy-mode:</span> <span class="hljs-string">XA</span><br></code></pre></td></tr></table></figure>
</li>
<li><p>给发起全局事务的入口方法添加<code>@GlobalTransactional</code>注解</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Slf4j</span><br><span class="hljs-meta">@Service</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">OrderServiceImpl</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">OrderService</span> &#123;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> AccountClient accountClient;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> StorageClient storageClient;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> OrderMapper orderMapper;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">OrderServiceImpl</span><span class="hljs-params">(AccountClient accountClient, StorageClient storageClient, OrderMapper orderMapper)</span> &#123;<br>        <span class="hljs-built_in">this</span>.accountClient = accountClient;<br>        <span class="hljs-built_in">this</span>.storageClient = storageClient;<br>        <span class="hljs-built_in">this</span>.orderMapper = orderMapper;<br>    &#125;<br><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-meta">@GlobalTransactional</span><br>    <span class="hljs-keyword">public</span> Long <span class="hljs-title function_">create</span><span class="hljs-params">(Order order)</span> &#123;<br>        <span class="hljs-comment">// 创建订单</span><br>        orderMapper.insert(order);<br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-comment">// 扣用户余额</span><br>            accountClient.deduct(order.getUserId(), order.getMoney());<br>            <span class="hljs-comment">// 扣库存</span><br>            storageClient.deduct(order.getCommodityCode(), order.getCount());<br><br>        &#125; <span class="hljs-keyword">catch</span> (FeignException e) &#123;<br>            log.error(<span class="hljs-string">&quot;下单失败，原因:&#123;&#125;&quot;</span>, e.contentUTF8(), e);<br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(e.contentUTF8(), e);<br>        &#125;<br>        <span class="hljs-keyword">return</span> order.getId();<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>
</li>
<li><p>重启服务并测试</p>
</li>
</ol>
<p><strong>运行前的数据</strong></p>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412080441953.png" srcset="/img/loading.gif" lazyload alt="image-20240412080441953"></p>
<p><strong>运行后的数据</strong></p>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412080512334.png" srcset="/img/loading.gif" lazyload alt="image-20240412080512334"></p>
<blockquote>
<p>可以看到实现了<code>全局事务</code></p>
</blockquote>
<h3 id="AT模式"><a href="#AT模式" class="headerlink" title="AT模式"></a>AT模式</h3><h4 id="概念-2"><a href="#概念-2" class="headerlink" title="概念"></a>概念</h4><blockquote>
<p>AT模式同样是分阶段提交的事务模型，不过缺弥补了XA模型中资源锁定周期过长的缺陷。</p>
</blockquote>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412081358601.png" srcset="/img/loading.gif" lazyload alt="image-20240412081358601"></p>
<blockquote>
<p>阶段一RM的工作:</p>
<ol>
<li>注册分支事务</li>
<li>记录<code>undo-log</code> (数据快照)</li>
<li>执行业务sql并提交</li>
<li>报告事务状态</li>
</ol>
<p>阶段二提交时RM的工作:<br>删除undo-log即可</p>
<p>阶段二回滚时RM的工作:<br>根据undo-log恢复数据到更新前</p>
</blockquote>
<p><strong>案例如下</strong></p>
<p>一个分支业务的SQL是这样的: </p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">update</span> tb account <span class="hljs-keyword">set</span> money <span class="hljs-operator">=</span> money<span class="hljs-operator">-</span> <span class="hljs-number">10</span> <span class="hljs-keyword">where</span> id <span class="hljs-operator">=</span> <span class="hljs-number">1</span><br></code></pre></td></tr></table></figure>

<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412081715928.png" srcset="/img/loading.gif" lazyload alt="image-20240412081715928"></p>
<h4 id="总结-2"><a href="#总结-2" class="headerlink" title="总结"></a>总结</h4><p>简述AT模式与XA模式最大的区别是什么?</p>
<ul>
<li>XA模式一阶段不提交事务，锁定资源; AT模式一阶段直接提交，不锁定资源。</li>
<li>XA模式依赖数据库机制实现回滚; AT模式利用数据快照实现数据回滚。</li>
<li><code>XA模式强一致</code>; <code>AT模式最终一致</code></li>
</ul>
<h4 id="AT模式的脏写问题"><a href="#AT模式的脏写问题" class="headerlink" title="AT模式的脏写问题"></a>AT模式的脏写问题</h4><p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412210846377.png" srcset="/img/loading.gif" lazyload alt="image-20240412210846377"></p>
<blockquote>
<p>事务1在提交事务之后，紧接着被事务2获取到了锁，而事务1的全局事务需要回滚，则此时事务1会将<code>money</code>恢复到100，但是影响到了事务2的操作</p>
<p>原因就是事务没有做到隔离，解决方式就是引入<code>全局锁</code>，标记对某行数据的拥有权，在事务提交前，其他事务无法修改</p>
</blockquote>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240412211854987.png" srcset="/img/loading.gif" lazyload alt="image-20240412211854987"></p>
<blockquote>
<p>事务1在提交事务后，释放DB锁；而DB锁马上被事务2获取到了，但全局事务锁在事务1手上，事务2拿不到</p>
<p>而DB锁在事务2手上，事务1拿不到，此时产生了死锁，但由于获取全局事务锁的过程有重试机制，任务超时会回滚并释放DB锁</p>
<p>这时事务1获取到了DB锁</p>
</blockquote>
<p><strong>有一个问题，如果在事务1的<code>1.3~2.1</code>过程中，有一个不被<code>seata</code>管理的事务修改了值，也会产生脏写问题</strong></p>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/QQ%E5%9B%BE%E7%89%8720240412212956.png" srcset="/img/loading.gif" lazyload></p>
<blockquote>
<p>在保存快照时，保存两份数据，一个是修改前的，一个是修改后的</p>
<p>当释放全局锁时，对比修改后的数据，如果不一致，说明在这段时间有其他事务修改了数据</p>
<p>这就没办法了，只能人工介入，这是比较极端的情况</p>
</blockquote>
<h4 id="总结-3"><a href="#总结-3" class="headerlink" title="总结"></a>总结</h4><p>AT模式的优点:</p>
<ol>
<li>一阶段完成直接提交事务,释放数据库资源，性能比较好</li>
<li>利用全局锁实现读写隔离</li>
<li>没有代码侵入,框架自动完成回滚和提交</li>
</ol>
<p>AT模式的缺点:</p>
<ol>
<li>两阶段之间属于软状态，属于最终一致</li>
<li>框架的快照功能会影响性能,但比XA模式要好很多</li>
</ol>
<h4 id="步骤"><a href="#步骤" class="headerlink" title="步骤"></a>步骤</h4><ol>
<li><p>新建两张表，其中lock_table导入到TC服务关联的数据库，undo_log表导入到微服务关联的数据库，这些操作在Seata配置阶段已经做过了</p>
</li>
<li><p>修改<code>application.yml</code>，将事务模式改为<code>AT</code>即可</p>
<figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">seata:</span><br>    <span class="hljs-attr">registry:</span><br>        <span class="hljs-attr">type:</span> <span class="hljs-string">nacos</span><br>        <span class="hljs-attr">nacos:</span><br>            <span class="hljs-attr">server-addr:</span> <span class="hljs-string">localhost:8848</span><br>            <span class="hljs-attr">namespace:</span> <span class="hljs-string">public</span><br>            <span class="hljs-attr">group:</span> <span class="hljs-string">DEFAULT_GROUP</span><br>            <span class="hljs-comment"># tc服务在nacos的注册名称</span><br>            <span class="hljs-attr">application:</span> <span class="hljs-string">seata-server</span><br>            <span class="hljs-attr">username:</span> <span class="hljs-string">nacos</span><br>            <span class="hljs-attr">password:</span> <span class="hljs-string">nacos</span><br>    <span class="hljs-comment"># 事务组，根据这个获取tc服务的cluster名称</span><br>    <span class="hljs-attr">tx-service-group:</span> <span class="hljs-string">seata-demo</span><br>    <span class="hljs-attr">service:</span><br>        <span class="hljs-comment"># 事务组与TC服务cluster的映射关系</span><br>        <span class="hljs-attr">vgroup-mapping:</span><br>            <span class="hljs-attr">seata-demo:</span> <span class="hljs-string">SH</span><br>    <span class="hljs-attr">data-source-proxy-mode:</span> <span class="hljs-string">AT</span><br></code></pre></td></tr></table></figure>


</li>
<li><p>重启服务测试</p>
</li>
</ol>
<h3 id="TCC模式"><a href="#TCC模式" class="headerlink" title="TCC模式"></a>TCC模式</h3><h4 id="概念-3"><a href="#概念-3" class="headerlink" title="概念"></a>概念</h4><p>TCC模式与AT模式非常相似,每阶段都是独立事务，不同的是TCC通过人工编码来实现数据恢复，而非自动恢复(生成快照)。需要实现三个方法:</p>
<ol>
<li>Try: 资源的检测和预留;</li>
<li>Confirm: 完成资源操作业务;要求Try成功Confirm一定要能成功。</li>
<li>Cancel: 预留资源释放,可以理解为try的反向操作。</li>
</ol>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240413063600514.png" srcset="/img/loading.gif" lazyload alt="image-20240413063600514"></p>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240413063809543.png" srcset="/img/loading.gif" lazyload alt="image-20240413063809543"></p>
<h4 id="总结-4"><a href="#总结-4" class="headerlink" title="总结"></a>总结</h4><p>TCC模式的每个阶段是做什么的?</p>
<ol>
<li>Try:资源检查和预留</li>
<li>Confirm:业务执行和提交</li>
<li>Cancel:预留资源的释放</li>
</ol>
<p>TCC的优点是什么?</p>
<ol>
<li>一阶段完成直接提交事务,释放数据库资源,性能好</li>
<li>相比AT模型，无需生成快照，无需使用全局锁，性能最强</li>
<li>不依赖数据库事务，而是依赖补偿操作，可以用于非事务型数据库</li>
</ol>
<p>TCC的缺点是什么?</p>
<ol>
<li>有代码侵入，需要人为编写try、Confirm和Cancel接口，太麻烦</li>
<li>软状态，事务是最终一致</li>
<li>需要考虑Confirm和Cancel的失败情况，做好<code>幂等处理</code>。(失败后，seata会<code>重试</code>，需要考虑重复处理的情况)</li>
</ol>
<h4 id="案例-1"><a href="#案例-1" class="headerlink" title="案例"></a>案例</h4><p>需求如下:</p>
<ol>
<li>修改account-service,编写try、confirm、 cancel逻辑</li>
<li>try业务:添加冻结金额，扣减可用金额</li>
<li>confirm业务:删除冻结金额</li>
<li>cancel业务:删除冻结金额，恢复可用金额</li>
<li>保证confirm、cancel接口的<code>幂等性</code>（业务接口不会因为重复调用出现问题）</li>
<li>允许<code>空回滚</code></li>
<li>拒绝<code>业务悬挂</code></li>
</ol>
<blockquote>
<p>有几个需要注意的点，<code>资源预留</code>是为了修改目标数据的值，但是新增订单是一个新增逻辑，这并<code>不适合TCC模式</code>，用<code>AT模式</code>就很好了</p>
<p>扣减余额和扣减库存可以使用TCC模式，在一个分布式事务当中既有<code>TCC模式</code>又有<code>AT模式</code>可以么？</p>
<p>答案是可以的</p>
</blockquote>
<h5 id="空回滚问题"><a href="#空回滚问题" class="headerlink" title="空回滚问题"></a>空回滚问题</h5><p>当某分支事务的try阶段阻塞时，可能导致全局事务超时而触发二阶段的cancel操作。在未执行try操作时先执行了cancel操作，这时cancel不能做回滚,就是<code>空回滚</code>。这里并没有进行<code>资源预留</code>也无法进行<code>回滚</code>，但是这里也不能进行报错，否则<code>Seata</code>会认为<code>cancel</code>失败，应该返回正常结束。</p>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240413065417807.png" srcset="/img/loading.gif" lazyload alt="image-20240413065417807"></p>
<h5 id="业务悬挂"><a href="#业务悬挂" class="headerlink" title="业务悬挂"></a>业务悬挂</h5><blockquote>
<p>此时阻塞的业务终于畅通了，但是这个事务已经提交了，后续既没有进行<code>confirm</code>也没有执行<code>cancel</code></p>
<p>对于已经空回滚的业务，如果以后继续执行try,就永远不可能confirm或cancel,这就是<code>业务悬挂</code>。应当阻止执行空回滚后的try操作，避免悬挂</p>
</blockquote>
<p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240413070301607.png" srcset="/img/loading.gif" lazyload alt="image-20240413070301607"></p>
<blockquote>
<p>为了避免<code>空回滚</code>，应该在<code>cancel</code>判断是否进行了<code>try</code></p>
<p>为了避免<code>业务悬挂</code>，应该在<code>try</code>判断是否进行了<code>cancel</code></p>
<p>这样就必须在数据库里记录当前状态，判断当前是在<code>try</code>？还是在<code>confirm</code>或者<code>cancel</code>？</p>
</blockquote>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-comment">-- auto-generated definition</span><br><span class="hljs-keyword">create</span> <span class="hljs-keyword">table</span> accoutn_freeze_tbl_8<br>(<br>    xid          <span class="hljs-type">varchar</span>(<span class="hljs-number">188</span>)           <span class="hljs-keyword">not</span> <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;事务编号&#x27;</span><br>        <span class="hljs-keyword">primary</span> key,<br>    user_id      <span class="hljs-type">varchar</span>(<span class="hljs-number">255</span>)           <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;用户编号`&#x27;</span>,<br>    freeze_money <span class="hljs-type">int</span> unsigned <span class="hljs-keyword">default</span> <span class="hljs-number">0</span> <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;冻结金额&#x27;</span>,<br>    state        <span class="hljs-type">int</span>                    <span class="hljs-keyword">null</span> comment <span class="hljs-string">&#x27;事务状态(0:try、1:confirm、2:cancel)&#x27;</span><br>)<br>    comment <span class="hljs-string">&#x27;冻结金额表&#x27;</span>;<br></code></pre></td></tr></table></figure>

<h6 id="try业务"><a href="#try业务" class="headerlink" title="try业务"></a>try业务</h6><ol>
<li>记录冻结金额和事务状态到account_ freeze表</li>
<li>扣减account表可用金额</li>
</ol>
<h6 id="confirm业务"><a href="#confirm业务" class="headerlink" title="confirm业务"></a>confirm业务</h6><p>根据xid删除account_freeze表的冻结记录</p>
<h6 id="cancel业务"><a href="#cancel业务" class="headerlink" title="cancel业务"></a>cancel业务</h6><ol>
<li>修改account_ freeze表,冻结金额为0，state为2</li>
<li>修改account表，恢复可用金额</li>
</ol>
<h6 id="如何判断是否空回滚"><a href="#如何判断是否空回滚" class="headerlink" title="如何判断是否空回滚"></a>如何判断是否空回滚</h6><p>cancel业务中，根据xid查询account_freeze,如果为null则说明try还没做，需要空回滚</p>
<h6 id="如何避免是否业务悬挂"><a href="#如何避免是否业务悬挂" class="headerlink" title="如何避免是否业务悬挂"></a>如何避免是否业务悬挂</h6><p>try业务中，根据xid查询account_freeze如果已经存在则证明Cancel已经执行，拒绝执行try业务</p>
<h5 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h5><p>TCC的<code>Try</code>、<code>Confirm</code>、 <code>Cancel</code>方法都需要在接口中基于注解来声明，语法如下:</p>
<blockquote>
<p>name指定<code>try</code>方法、commitMethod指定<code>confirm</code>方法、rollbackMethod指定<code>cancel</code>方法</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">AccountTCCService</span> &#123;<br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 从用户账户中扣款</span><br><span class="hljs-comment">     *</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> userId 用户 ID</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> money  钱</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-meta">@TwoPhaseBusinessAction(name = &quot;deduct&quot;, commitMethod = &quot;confirm&quot;, rollbackMethod = &quot;cancel&quot;)</span><br>    <span class="hljs-keyword">void</span> <span class="hljs-title function_">deduct</span><span class="hljs-params">(<span class="hljs-meta">@BusinessActionContextParameter(paramName = &quot;userId&quot;)</span> String userId,</span><br><span class="hljs-params">                <span class="hljs-meta">@BusinessActionContextParameter(paramName = &quot;money&quot;)</span> <span class="hljs-type">int</span> money)</span>;<br><br>    <span class="hljs-type">boolean</span> <span class="hljs-title function_">confirm</span><span class="hljs-params">(BusinessActionContext context)</span>;<br><br>    <span class="hljs-type">boolean</span> <span class="hljs-title function_">cancel</span><span class="hljs-params">(BusinessActionContext context)</span>;<br>&#125;<br></code></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Data</span><br><span class="hljs-meta">@TableName(&quot;account_freeze_tbl&quot;)</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">AccountFreeze</span> &#123;<br>    <span class="hljs-meta">@TableId(type = IdType.INPUT)</span><br>    <span class="hljs-keyword">private</span> String xid;<br>    <span class="hljs-keyword">private</span> String userId;<br>    <span class="hljs-keyword">private</span> Integer freezeMoney;<br>    <span class="hljs-keyword">private</span> Integer state;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">State</span> &#123;<br>        <span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">static</span> <span class="hljs-type">int</span> <span class="hljs-variable">TRY</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;<br>        <span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">static</span> <span class="hljs-type">int</span> <span class="hljs-variable">CONFIRM</span> <span class="hljs-operator">=</span> <span class="hljs-number">1</span>;<br>        <span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">static</span> <span class="hljs-type">int</span> <span class="hljs-variable">CANCEL</span> <span class="hljs-operator">=</span> <span class="hljs-number">2</span>;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">AccountFreezeMapper</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">BaseMapper</span>&lt;AccountFreeze&gt; &#123;<br>&#125;<br></code></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Slf4j</span><br><span class="hljs-meta">@Service</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">AccountTCCServiceImpl</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">AccountTCCService</span> &#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> AccountMapper accountMapper;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> AccountFreezeMapper accountFreezeMapper;<br><br>    <span class="hljs-meta">@Autowired</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">AccountTCCServiceImpl</span><span class="hljs-params">(AccountMapper accountMapper, AccountFreezeMapper accountFreezeMapper)</span> &#123;<br>        <span class="hljs-built_in">this</span>.accountMapper = accountMapper;<br>        <span class="hljs-built_in">this</span>.accountFreezeMapper = accountFreezeMapper;<br>    &#125;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 扣除金额</span><br><span class="hljs-comment">     * TCC try方法</span><br><span class="hljs-comment">     *</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> userId 用户 ID</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> money  钱</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-meta">@Transactional</span><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">deduct</span><span class="hljs-params">(String userId, <span class="hljs-type">int</span> money)</span> &#123;<br>        <span class="hljs-comment">// 获取事务id</span><br>        <span class="hljs-type">String</span> <span class="hljs-variable">xid</span> <span class="hljs-operator">=</span> RootContext.getXID();<br>        <span class="hljs-comment">// 数据库字段设置为unsigned int，所以不能为负数</span><br>        <span class="hljs-comment">// 1.扣减金额</span><br>        accountMapper.deduct(userId,money);<br>        <span class="hljs-comment">// 2.记录冻结金额、事务状态</span><br>        <span class="hljs-type">AccountFreeze</span> <span class="hljs-variable">accountFreeze</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">AccountFreeze</span>();<br>        accountFreeze.setUserId(userId);<br>        accountFreeze.setFreezeMoney(money);<br>        accountFreeze.setState(AccountFreeze.State.TRY);<br>        accountFreeze.setXid(xid);<br>        accountFreezeMapper.insert(accountFreeze);<br>    &#125;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 确认</span><br><span class="hljs-comment">     * TCC confirm方法</span><br><span class="hljs-comment">     *</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> context 上下文</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@return</span> boolean</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">confirm</span><span class="hljs-params">(BusinessActionContext context)</span> &#123;<br>        <span class="hljs-comment">// 获取事务id</span><br>        <span class="hljs-type">String</span> <span class="hljs-variable">xid</span> <span class="hljs-operator">=</span> context.getXid();<br>        <span class="hljs-comment">// 删除了一条数据</span><br>        <span class="hljs-keyword">return</span> accountFreezeMapper.deleteById(xid)==<span class="hljs-number">1</span>;<br>    &#125;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 取消</span><br><span class="hljs-comment">     * TCC cancel方法</span><br><span class="hljs-comment">     *</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> context 上下文</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@return</span> boolean</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">cancel</span><span class="hljs-params">(BusinessActionContext context)</span> &#123;<br>        <span class="hljs-comment">// 查询冻结记录</span><br>        <span class="hljs-type">String</span> <span class="hljs-variable">xid</span> <span class="hljs-operator">=</span> context.getXid();<br>        <span class="hljs-type">AccountFreeze</span> <span class="hljs-variable">accountFreeze</span> <span class="hljs-operator">=</span> accountFreezeMapper.selectById(xid);<br>        <span class="hljs-comment">// 1.恢复可用余额</span><br>        accountMapper.refund(accountFreeze.getUserId(),accountFreeze.getFreezeMoney());<br>        <span class="hljs-comment">// 2.将冻结金额清零，状态设置为CANCEL</span><br>        accountFreeze.setFreezeMoney(<span class="hljs-number">0</span>);<br>        accountFreeze.setState(AccountFreeze.State.CANCEL);<br>        <span class="hljs-type">int</span> <span class="hljs-variable">count</span> <span class="hljs-operator">=</span> accountFreezeMapper.updateById(accountFreeze);<br>        <span class="hljs-keyword">return</span> count==<span class="hljs-number">1</span>;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<blockquote>
<p>以上是基本内容</p>
<p>接下来增加对于<code>空回滚</code>和<code>业务悬挂</code>的判断</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Slf4j</span><br><span class="hljs-meta">@Service</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">AccountTCCServiceImpl</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">AccountTCCService</span> &#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> AccountMapper accountMapper;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> AccountFreezeMapper accountFreezeMapper;<br><br>    <span class="hljs-meta">@Autowired</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">AccountTCCServiceImpl</span><span class="hljs-params">(AccountMapper accountMapper, AccountFreezeMapper accountFreezeMapper)</span> &#123;<br>        <span class="hljs-built_in">this</span>.accountMapper = accountMapper;<br>        <span class="hljs-built_in">this</span>.accountFreezeMapper = accountFreezeMapper;<br>    &#125;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 扣除金额</span><br><span class="hljs-comment">     * TCC try方法</span><br><span class="hljs-comment">     *</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> userId 用户 ID</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> money  钱</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-meta">@Transactional</span><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">deduct</span><span class="hljs-params">(String userId, <span class="hljs-type">int</span> money)</span> &#123;<br>        <span class="hljs-comment">// 获取事务id</span><br>        <span class="hljs-type">String</span> <span class="hljs-variable">xid</span> <span class="hljs-operator">=</span> RootContext.getXID();<br>        <span class="hljs-comment">// 判断业务悬挂</span><br>        <span class="hljs-type">AccountFreeze</span> <span class="hljs-variable">selectedById</span> <span class="hljs-operator">=</span> accountFreezeMapper.selectById(xid);)<br>        <span class="hljs-keyword">if</span>(selectedById!=<span class="hljs-literal">null</span>)&#123;<br>            <span class="hljs-comment">// cancel已经执行过</span><br>            <span class="hljs-keyword">return</span>;<br>        &#125;<br>        <span class="hljs-comment">// 数据库字段设置为unsigned int，所以不能为负数</span><br>        <span class="hljs-comment">// 1.扣减金额</span><br>        accountMapper.deduct(userId,money);<br>        <span class="hljs-comment">// 2.记录冻结金额、事务状态</span><br>        <span class="hljs-type">AccountFreeze</span> <span class="hljs-variable">accountFreeze</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">AccountFreeze</span>();<br>        accountFreeze.setUserId(userId);<br>        accountFreeze.setFreezeMoney(money);<br>        accountFreeze.setState(AccountFreeze.State.TRY);<br>        accountFreeze.setXid(xid);<br>        accountFreezeMapper.insert(accountFreeze);<br>    &#125;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 确认</span><br><span class="hljs-comment">     * TCC confirm方法</span><br><span class="hljs-comment">     *</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> context 上下文</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@return</span> boolean</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">confirm</span><span class="hljs-params">(BusinessActionContext context)</span> &#123;<br>        <span class="hljs-comment">// 获取事务id</span><br>        <span class="hljs-type">String</span> <span class="hljs-variable">xid</span> <span class="hljs-operator">=</span> context.getXid();<br>        <span class="hljs-comment">// 删除了一条数据，删除数据本来就是幂等性的</span><br>        <span class="hljs-keyword">return</span> accountFreezeMapper.deleteById(xid)==<span class="hljs-number">1</span>;<br>    &#125;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 取消</span><br><span class="hljs-comment">     * TCC cancel方法</span><br><span class="hljs-comment">     *</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> context 上下文</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@return</span> boolean</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">cancel</span><span class="hljs-params">(BusinessActionContext context)</span> &#123;<br>        <span class="hljs-comment">// 查询冻结记录</span><br>        <span class="hljs-type">String</span> <span class="hljs-variable">xid</span> <span class="hljs-operator">=</span> context.getXid();<br>        <span class="hljs-type">String</span> <span class="hljs-variable">userId</span> <span class="hljs-operator">=</span> context.getActionContext(<span class="hljs-string">&quot;userId&quot;</span>).toString();<br>        <span class="hljs-type">AccountFreeze</span> <span class="hljs-variable">accountFreeze</span> <span class="hljs-operator">=</span> accountFreezeMapper.selectById(xid);<br>        <span class="hljs-comment">// 空回滚的判断</span><br>        <span class="hljs-keyword">if</span>(accountFreeze==<span class="hljs-literal">null</span>)&#123;<br>            <span class="hljs-comment">// 记录空回滚的数据</span><br>            accountFreeze.setUserId(userId);<br>            accountFreeze.setFreezeMoney(<span class="hljs-number">0</span>);<br>            accountFreeze.setState(AccountFreeze.State.CANCEL);<br>            accountFreeze.setXid(xid);<br>            accountFreezeMapper.insert(accountFreeze);<br>            <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br>        &#125;<br>        <span class="hljs-comment">// 幂等性判断</span><br>        <span class="hljs-keyword">if</span>(accountFreeze.getState()==AccountFreeze.State.CANCEL)&#123;<br>            <span class="hljs-comment">// 已经执行过cancel方法了，无需重复执行</span><br>            <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br>        &#125;<br>        <span class="hljs-comment">// 1.恢复可用余额</span><br>        accountMapper.refund(accountFreeze.getUserId(),accountFreeze.getFreezeMoney());<br>        <span class="hljs-comment">// 2.将冻结金额清零，状态设置为CANCEL</span><br>        accountFreeze.setFreezeMoney(<span class="hljs-number">0</span>);<br>        accountFreeze.setState(AccountFreeze.State.CANCEL);<br>        <span class="hljs-type">int</span> <span class="hljs-variable">count</span> <span class="hljs-operator">=</span> accountFreezeMapper.updateById(accountFreeze);<br>        <span class="hljs-keyword">return</span> count==<span class="hljs-number">1</span>;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<h3 id="Saga模式"><a href="#Saga模式" class="headerlink" title="Saga模式"></a>Saga模式</h3><p>Saga模式是SEATA提供的长事务解决方案。也分为两个阶段:</p>
<ol>
<li>一阶段:直接提交本地事务</li>
<li>二阶段:成功则什么都不做;失败则通过编写补偿业务来回滚</li>
</ol>
<p>Saga模式的缺点是没有隔离性</p>
<p>Saga模式优点:</p>
<ol>
<li>事务参与者可以基于事件驱动实现异步调用，吞吐高</li>
<li>一阶段直接提交事务，无锁，性能好</li>
<li>不用编写TCC中的三个阶段，实现简单</li>
</ol>
<p>缺点:</p>
<ol>
<li>软状态持续时间不确定，时效性差</li>
<li>没有锁，没有事务隔离，会有脏写</li>
</ol>
<h3 id="模式对比"><a href="#模式对比" class="headerlink" title="模式对比"></a>模式对比</h3><p><img src="https://gitee.com/tongstyle/blog-img/raw/master/img/image-20240413205630731.png" srcset="/img/loading.gif" lazyload alt="image-20240413205630731"></p>

                
              </div>
            
            <hr/>
            <div>
              <div class="post-metas my-3">
  
    <div class="post-meta mr-3 d-flex align-items-center">
      <i class="iconfont icon-category"></i>
      

<span class="category-chains">
  
  
    
      <span class="category-chain">
        
  <a href="/categories/Seata/" class="category-chain-item">Seata</a>
  
  

      </span>
    
  
</span>

    </div>
  
  
    <div class="post-meta">
      <i class="iconfont icon-tags"></i>
      
        <a href="/tags/Seata/">#Seata</a>
      
    </div>
  
</div>


              
  

  <div class="license-box my-3">
    <div class="license-title">
      <div>Seata</div>
      <div>http://example.com/2024/04/16/编程语言/Java/学习/框架/Seata/Seata/</div>
    </div>
    <div class="license-meta">
      
        <div class="license-meta-item">
          <div>作者</div>
          <div>深海火锅店</div>
        </div>
      
      
        <div class="license-meta-item license-meta-date">
          <div>发布于</div>
          <div>2024年4月16日</div>
        </div>
      
      
      
        <div class="license-meta-item">
          <div>许可协议</div>
          <div>
            
              
              
                <a target="_blank" href="https://creativecommons.org/licenses/by/4.0/">
                  <span class="hint--top hint--rounded" aria-label="BY - 署名">
                    <i class="iconfont icon-by"></i>
                  </span>
                </a>
              
            
          </div>
        </div>
      
    </div>
    <div class="license-icon iconfont"></div>
  </div>



              
                <div class="post-prevnext my-3">
                  <article class="post-prev col-6">
                    
                    
                  </article>
                  <article class="post-next col-6">
                    
                    
                      <a href="/2024/04/15/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/Java/%E5%AD%A6%E4%B9%A0/%E6%A1%86%E6%9E%B6/Resilience4j/Resilience4j/" title="Resilience4j">
                        <span class="hidden-mobile">Resilience4j</span>
                        <span class="visible-mobile">下一篇</span>
                        <i class="iconfont icon-arrowright"></i>
                      </a>
                    
                  </article>
                </div>
              
            </div>

            
          </article>
        </div>
      </div>
    </div>

    <div class="side-col d-none d-lg-block col-lg-2">
      
  <aside class="sidebar" style="margin-left: -1rem">
    <div id="toc">
  <p class="toc-header"><i class="iconfont icon-list"></i>&nbsp;目录</p>
  <div class="toc-body" id="toc-body"></div>
</div>



  </aside>


    </div>
  </div>
</div>





  



  



  



  



  







    

    
      <a id="scroll-top-button" aria-label="TOP" href="#" role="button">
        <i class="iconfont icon-arrowup" aria-hidden="true"></i>
      </a>
    

    
      <div class="modal fade" id="modalSearch" tabindex="-1" role="dialog" aria-labelledby="ModalLabel"
     aria-hidden="true">
  <div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
    <div class="modal-content">
      <div class="modal-header text-center">
        <h4 class="modal-title w-100 font-weight-bold">搜索</h4>
        <button type="button" id="local-search-close" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body mx-3">
        <div class="md-form mb-5">
          <input type="text" id="local-search-input" class="form-control validate">
          <label data-error="x" data-success="v" for="local-search-input">关键词</label>
        </div>
        <div class="list-group" id="local-search-result"></div>
      </div>
    </div>
  </div>
</div>

    

    
  </main>

  <footer>
    <div class="footer-inner">
  
    <div class="footer-content">
       <a href="https://hexo.io" target="_blank" rel="nofollow noopener"><span>Hexo</span></a> <i class="iconfont icon-love"></i> <a href="https://github.com/fluid-dev/hexo-theme-fluid" target="_blank" rel="nofollow noopener"><span>Fluid</span></a> 
    </div>
  
  
    <div class="statistics">
  
  

  
    
      <span id="busuanzi_container_site_pv" style="display: none">
        总访问量 
        <span id="busuanzi_value_site_pv"></span>
         次
      </span>
    
    
      <span id="busuanzi_container_site_uv" style="display: none">
        总访客数 
        <span id="busuanzi_value_site_uv"></span>
         人
      </span>
    
    
  
</div>

  
  
  
</div>

  </footer>

  <!-- Scripts -->
  
  <script  src="https://lib.baomitu.com/nprogress/0.2.0/nprogress.min.js" ></script>
  <link  rel="stylesheet" href="https://lib.baomitu.com/nprogress/0.2.0/nprogress.min.css" />

  <script>
    NProgress.configure({"showSpinner":false,"trickleSpeed":100})
    NProgress.start()
    window.addEventListener('load', function() {
      NProgress.done();
    })
  </script>


<script  src="https://lib.baomitu.com/jquery/3.6.0/jquery.min.js" ></script>
<script  src="https://lib.baomitu.com/twitter-bootstrap/4.6.1/js/bootstrap.min.js" ></script>
<script  src="/js/events.js" ></script>
<script  src="/js/plugins.js" ></script>


  <script  src="https://lib.baomitu.com/typed.js/2.0.12/typed.min.js" ></script>
  <script>
    (function (window, document) {
      var typing = Fluid.plugins.typing;
      var subtitle = document.getElementById('subtitle');
      if (!subtitle || !typing) {
        return;
      }
      var text = subtitle.getAttribute('data-typed-text');
      
        typing(text);
      
    })(window, document);
  </script>




  
    <script  src="/js/img-lazyload.js" ></script>
  




  
<script>
  Fluid.utils.createScript('https://lib.baomitu.com/tocbot/4.18.2/tocbot.min.js', function() {
    var toc = jQuery('#toc');
    if (toc.length === 0 || !window.tocbot) { return; }
    var boardCtn = jQuery('#board-ctn');
    var boardTop = boardCtn.offset().top;

    window.tocbot.init(Object.assign({
      tocSelector     : '#toc-body',
      contentSelector : '.markdown-body',
      linkClass       : 'tocbot-link',
      activeLinkClass : 'tocbot-active-link',
      listClass       : 'tocbot-list',
      isCollapsedClass: 'tocbot-is-collapsed',
      collapsibleClass: 'tocbot-is-collapsible',
      scrollSmooth    : true,
      includeTitleTags: true,
      headingsOffset  : -boardTop,
    }, CONFIG.toc));
    if (toc.find('.toc-list-item').length > 0) {
      toc.css('visibility', 'visible');
    }

    Fluid.events.registerRefreshCallback(function() {
      if ('tocbot' in window) {
        tocbot.refresh();
        var toc = jQuery('#toc');
        if (toc.length === 0 || !tocbot) {
          return;
        }
        if (toc.find('.toc-list-item').length > 0) {
          toc.css('visibility', 'visible');
        }
      }
    });
  });
</script>


  <script src=https://lib.baomitu.com/clipboard.js/2.0.11/clipboard.min.js></script>

  <script>Fluid.plugins.codeWidget();</script>


  
<script>
  Fluid.utils.createScript('https://lib.baomitu.com/anchor-js/4.3.1/anchor.min.js', function() {
    window.anchors.options = {
      placement: CONFIG.anchorjs.placement,
      visible  : CONFIG.anchorjs.visible
    };
    if (CONFIG.anchorjs.icon) {
      window.anchors.options.icon = CONFIG.anchorjs.icon;
    }
    var el = (CONFIG.anchorjs.element || 'h1,h2,h3,h4,h5,h6').split(',');
    var res = [];
    for (var item of el) {
      res.push('.markdown-body > ' + item.trim());
    }
    if (CONFIG.anchorjs.placement === 'left') {
      window.anchors.options.class = 'anchorjs-link-left';
    }
    window.anchors.add(res.join(', '));

    Fluid.events.registerRefreshCallback(function() {
      if ('anchors' in window) {
        anchors.removeAll();
        var el = (CONFIG.anchorjs.element || 'h1,h2,h3,h4,h5,h6').split(',');
        var res = [];
        for (var item of el) {
          res.push('.markdown-body > ' + item.trim());
        }
        if (CONFIG.anchorjs.placement === 'left') {
          anchors.options.class = 'anchorjs-link-left';
        }
        anchors.add(res.join(', '));
      }
    });
  });
</script>


  
<script>
  Fluid.utils.createScript('https://lib.baomitu.com/fancybox/3.5.7/jquery.fancybox.min.js', function() {
    Fluid.plugins.fancyBox();
  });
</script>


  <script>Fluid.plugins.imageCaption();</script>

  <script  src="/js/local-search.js" ></script>

  <script defer src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js" ></script>




  
<script src="//cdn.jsdelivr.net/gh/EmoryHuang/BlogBeautify@1.1/DynamicRibbon.min.js"></script>



<!-- 主题的启动项，将它保持在最底部 -->
<!-- the boot of the theme, keep it at the bottom -->
<script  src="/js/boot.js" ></script>


  

  <noscript>
    <div class="noscript-warning">博客在允许 JavaScript 运行的环境下浏览效果更佳</div>
  </noscript>
</body>
</html>
